winbrew_app\operations\doctor\scan/
mod.rs

1//! Shared coordination for doctor scan sources.
2//!
3//! Each submodule owns a single scan source, while this module provides the
4//! shared result container, sorting helpers, and the entry points used by the
5//! doctor workflow.
6
7use crate::models::domains::installed::InstalledPackage;
8use crate::models::domains::reporting::{DiagnosisResult, DiagnosisSeverity, RecoveryFinding};
9
10mod journal;
11mod msi;
12mod orphan;
13mod package;
14
15pub(super) use msi::{MsiInventoryScan, scan_msi_inventory};
16pub(super) use package::{PackageInstallScan, installed_packages, scan_packages};
17
18#[derive(Debug, Default)]
19pub(super) struct ScanResult {
20    pub(super) diagnostics: Vec<DiagnosisResult>,
21    pub(super) recovery_findings: Vec<RecoveryFinding>,
22}
23
24impl ScanResult {
25    fn push(&mut self, diagnosis: DiagnosisResult, target_path: Option<&std::path::Path>) {
26        if let Some(finding) = RecoveryFinding::from_diagnosis(&diagnosis) {
27            let finding = match target_path {
28                Some(target_path) => {
29                    finding.with_target_path(target_path.to_string_lossy().into_owned())
30                }
31                None => finding,
32            };
33            self.recovery_findings.push(finding);
34        }
35
36        self.diagnostics.push(diagnosis);
37    }
38
39    fn push_orphan(&mut self, package_name: &str, path: &std::path::Path) {
40        let diagnosis = DiagnosisResult {
41            error_code: "orphan_install_directory".to_string(),
42            description: format!(
43                "{}: orphan install directory ({})",
44                package_name,
45                path.to_string_lossy()
46            ),
47            severity: DiagnosisSeverity::Warning,
48        };
49
50        self.push(diagnosis, Some(path));
51    }
52}
53
54pub(super) type PackageJournalScan = ScanResult;
55pub(super) type OrphanInstallScan = ScanResult;
56
57pub(super) fn scan_package_journals(
58    paths: &crate::core::paths::ResolvedPaths,
59    packages: &[InstalledPackage],
60) -> PackageJournalScan {
61    journal::scan_package_journals(paths, packages)
62}
63
64pub(super) fn scan_orphaned_install_dirs(
65    packages_root: &std::path::Path,
66    packages: &[InstalledPackage],
67) -> OrphanInstallScan {
68    orphan::scan_orphaned_install_dirs(packages_root, packages)
69}
70
71/// Sort diagnostics deterministically by code and description.
72pub(super) fn sort_diagnoses(diagnoses: &mut [DiagnosisResult]) {
73    diagnoses.sort_unstable_by(|left, right| {
74        left.error_code
75            .cmp(&right.error_code)
76            .then_with(|| left.description.cmp(&right.description))
77    });
78}
79
80pub(super) fn sort_recovery_findings(findings: &mut [RecoveryFinding]) {
81    findings.sort_unstable_by(|left, right| {
82        left.severity
83            .cmp(&right.severity)
84            .then_with(|| left.error_code.cmp(&right.error_code))
85            .then_with(|| left.target_path.cmp(&right.target_path))
86            .then_with(|| left.description.cmp(&right.description))
87    });
88}